package ws

import (
	"encoding/hex"
	"errors"
	"sync"
	"time"

	"gitee.com/crack007/goose/gsjob"
	"gitee.com/crack007/goose/gslog"
	"gitee.com/crack007/goose/gsutil"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

type Session struct {
	conn             *websocket.Conn
	SessionId        int64
	sessionHandle    ISessionHandle
	Ctx              *gin.Context
	readIdleTimeout  time.Duration // 读空闲超时时间
	writeIdleTimeout time.Duration // 写空闲超时时间
	idleTimeout      time.Duration // 空闲超时时间
	lastReadAt       time.Time
	lastWriteAt      time.Time
	lastActiveAt     time.Time
	readIdleJob      *SessionJob
	writeIdleJob     *SessionJob
	idleJob          *SessionJob
	RemoteIp         string
	RemotePort       uint16
	messageType      int
	Tag              string // 会话标签，便于管理
	Server           *Server
	sessionClose     sync.Once
}

func (x *Session) Conn() *websocket.Conn {
	return x.conn
}

func NewSession(conn *websocket.Conn, sessionHandle ISessionHandle, ctx *gin.Context) *Session {
	session := &Session{
		conn:          conn,
		sessionHandle: sessionHandle,
		Ctx:           ctx,
	}
	return session
}

func (x *Session) init() error {
	x.RemoteIp, x.RemotePort = gsutil.IpUtil.ParseFromAddr(x.conn.RemoteAddr())
	x.sessionHandle.Init(x)
	if !x.sessionHandle.OnHandshake(x) {
		x.Close()
		return errors.New("connect error")
	}

	x.SessionId = x.sessionHandle.GetSessionId(x)
	x.Server.SessionManager.addSession(x)
	x.sessionHandle.OnConnect(x)
	return nil
}

func (x *Session) close(err error) {
	x.sessionClose.Do(func() {
		defer func() {
			if x.readIdleJob != nil {
				x.readIdleJob.Cancel()
			}

			if x.writeIdleJob != nil {
				x.writeIdleJob.Cancel()
			}

			if x.idleJob != nil {
				x.idleJob.Cancel()
			}

			x.Server.SessionManager.removeSession(x)
			x.conn.Close()
		}()
		x.sessionHandle.OnClose(x, err)
	})
}

func (x *Session) Close() {
	x.close(nil)
}

func (x *Session) Process() (err error) {
	err = x.init()
	if err != nil {
		return err
	}

	x.pushJob()
	defer x.close(err)

	for {
		wsPackage := &WsPackage{}
		wsPackage.MessageType, wsPackage.Data, err = x.conn.ReadMessage()
		if err != nil {
			if v, ok := err.(*websocket.CloseError); ok {
				gslog.Warn(v.Error())
			}
			break
		}
		// 统一的channel投递消费消息，避免每一个会话都创建channel
		x.Server.MessageManager.Read(x, wsPackage)
	}

	return err
}

func (x *Session) Write(wsPackage *WsPackage) {
	x.Server.MessageManager.Write(x, wsPackage)
}

func (x *Session) write(wsPackage *WsPackage) {
	err := x.conn.WriteMessage(wsPackage.MessageType, wsPackage.Data)
	if err != nil {
		panic(err)
	}

	x.lastWriteAt = time.Now()
	x.lastActiveAt = x.lastWriteAt
}

func (x *Session) read(wsPackage *WsPackage) {
	gslog.Trace("[%d] OnMessage-> msgType:%d length:%d\r\n%x", x.SessionId, wsPackage.MessageType, len(wsPackage.Data), hex.Dump(wsPackage.Data))
	x.sessionHandle.OnMessage(x, wsPackage)
	if isData(wsPackage.MessageType) {
		x.sessionHandle.OnData(x, wsPackage)
	}

	x.lastReadAt = time.Now()
	x.lastActiveAt = x.lastReadAt
}

// 投递空闲事件检测任务
func (x *Session) pushJob() {
	if x.readIdleTimeout > 0 {
		x.readIdleJob = NewSessionJob(NewSessionReadIdleTimeout(x),
			"readIdleJob",
			&gsjob.ScheduleConfig{Every: x.readIdleTimeout})
		x.readIdleJob.Submit()
	}

	if x.writeIdleTimeout > 0 {
		x.writeIdleJob = NewSessionJob(NewSessionWriteIdleTimeout(x),
			"writeIdleJob",
			&gsjob.ScheduleConfig{Every: x.writeIdleTimeout})
		x.writeIdleJob.Submit()
	}

	if x.idleTimeout > 0 {
		x.idleJob = NewSessionJob(NewSessionIdleTimeout(x),
			"idleJob",
			&gsjob.ScheduleConfig{Every: x.idleTimeout})
		x.idleJob.Submit()
	}
}
